<html>
<head>
  <title>22.模板方法（Template Method）模式</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/602628 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="26642"/>
<h1>22.模板方法（Template Method）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/10/11 5:33</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/16 7:48</i></td></tr>
<tr><td><b>作者：</b></td><td><i>YuGe</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 12pt; font-weight: bold;">一、基本描述</span></div><ul><li><div><span style="font-size: 12pt;">定义一个操作中的算法的骨架，而将一些步骤延迟到子类中。 TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。</span></div></li><li><div><span style="font-size: 16px;">模板方法定义了一个算法的步骤，并允许子类为一个或多个步骤提供实现。</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">模板方法与具体方法</span></span></div></li><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">模板方法</span><span style="font-size: 16px; font-weight: bold;">：</span>一个模板方法用一些抽象的操作定义一个算法，而子类将冲定义这些操作以提供具体的行为。通过使用抽象操作定义一个算法中的一些步骤，模板方法确定了它们的先后顺序，但它允许子类改变这些具体步骤以满足它们各自的需求。【模板方法的精髓是模板方法本身是不可变的，但是其中调用的操作可以由子类改变，即</span><span style="font-size: 16px; color: unset; font-family: unset;">每一个具体步骤肯定是可变的，但步骤本身不可变】。</span></div><div><span style="font-size: 12pt;">一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法。这个模板方法定义在抽象类中，并由子类不加以修改地完全继承下来。</span></div><div><span style="font-size: 12pt;">模板方法是一个具体方法，它给出了一个顶层逻辑框架，而逻辑的组成步骤在抽象类中可以是具体方法，也可以是抽象方法。由于模板方法是具体方法，因此模板方法模式中的抽象层只能是抽象类，而不是接口。</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">基本方法：</span>基本方法是实现算法各个步骤的方法，是模板方法的组成部分。基本方法可分为三种：</span></div></li><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">抽象方法</span>：</span><span style="font-size: 12pt;">一个抽象方法由抽象类声明、由其具体子类实现。【也就是所谓的原语操作，主要体现动态绑定，体现不同的子类不同，是子类必须重新实现的方法】</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">具体方法</span>：</span><span style="font-size: 12pt;">一个具体方法由一个抽象类或具体类声明并实现，其子类可以进行覆盖也可以直接继承。【主要体现为子类中的公共操作，这些操作抽取到基类当中，子类可以修改，但一般不应该修改】</span></div></li><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">钩子方法</span>：</span><span style="font-size: 12pt;">一个钩子方法由一个抽象类或具体类声明并实现，而其子类可能会加以扩展。通常在父类中给出的实现是一个空实现，并以该空实现作为方法的默认实现，当然钩子方法也可以提供一个非空的默认实现。【基类中有实现，子类中可能会修改的特殊的具体方法】</span></div></li><ul><li><div><span style="font-size: 16px; color: unset; font-family: unset;">钩子是一种被声明在抽象类中的方法，但只有空的或者默认的实现。钩子的存在，可以让子类有能力对算法的不同点进行挂钩。要不要挂钩，由子类自行决定。</span></div></li><li><div><span style="font-size: 12pt;">它提供了缺省的行为，子类可以在必要时进行扩展。</span></div></li><li><div><span style="font-size: 16px;">钩子方法的引入使得子类可以控制父类的行为。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">很重要的一点是模板方法应该指明哪些操作是钩子操作（可以被重定义）以及哪些是抽象操作（必须被重定义）。要有效地复用一个抽象类，子类编写者必须明确了解哪些操作是设计为有待重定义的。</span></div></li><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">子类可以通过重定义父类的操作来扩展该操作的行为，其间可显式地调用父类操作【一般思路】。</span><span style="font-size: 12pt; color: unset; font-family: unset;">不幸的是，人们很容易忘记去调用被继承的行为。我们可以将这样一个操作转换为一个模板方法，以使得父类可以对子类的扩展方式进行控制。也就是，在父类的模板方法中调用钩子操作，子类可以重定义这个钩子操作。</span></div></li><li><div><span style="font-size: 16px;">钩子的用法：</span></div></li><ul><li><div><span style="font-size: 16px;">钩子可以让子类实现算法中可选的部分。</span><span style="font-size: 16px; color: unset; font-family: unset;">在钩子对于子类实现不重要的时候，子类可以对此钩子置之不理。</span></div></li><li><div><span style="font-size: 16px;">让子类能够有机会对模板方法中某些即将发生的（或者刚刚发生的）步骤做出反应。</span></div></li><li><div><span style="font-size: 16px;">钩子可以让子类有能力为其抽象类作一些决定。</span></div></li></ul><li><div><span style="font-size: 16px; color: unset; font-family: unset;">钩子方法有两类：</span></div></li></ul><ul><ul><li><div><span style="font-size: 12pt;">一类钩子方法可以与一些具体步骤“挂钩”，以实现在不同条件下执行模板方法中的不同步骤，这类钩子方法的返回类型通常是bool类型的，这类方法名一般为IsXXX()，用于对某个条件进行判断，如果条件满足则执行某一步骤，否则将不执行。</span></div></li><li><div><span style="font-size: 16px;">另一类</span><span style="font-size: 12pt;">钩子方法就是实现体为空的具体方法，子类可以根据需要覆盖或者继承这些钩子方法，与抽象方法相比，这类钩子方法的好处在于子类如果没有覆盖父类中定义的钩子方法，编译可以正常通过，但是如果没有覆盖父类中声明的抽象方法，编译将报错。</span></div></li></ul></ul><li><div><span style="font-size: 16px;">当子类“必须”提供算法中某个方法或步骤的实现时，就使用抽象方法，如果算法的这个部分是可选的，就用钩子，如果是钩子的话，子类可以选择实现这个钩子，但并不强制这么做。</span></div></li></ul></ul><li><div><span style="font-size: 12pt;">模板方法是一种代码复用的基本技术。它们在类库中尤为重要，它们提取了类库中的公共行为。</span></div></li><li><div><span style="font-size: 16px;">模板方法模式用来创建一个算法的模板。</span></div><div><span style="font-size: 16px;">模板：模板是一个方法。更具体的说，这个方法将算法定义成一组步骤，其中的任何步骤都可以是抽象的，由子类负责实现。这可以确保算法的结构保持不变，同时由子类提供部分实现。</span></div></li><li><div><span style="font-size: 12pt;">模板方法导致一种反向的控制结构，这种结构有时被称为“好莱坞法则”，即“别找我们，我们找你”。这指的是一个父类调用一个子类的操作，而不是相反。</span></div></li><li><div><span style="font-size: 12pt;">既然用了继承，并且肯定这个继承有意义，就应该要成为子类的模板，所有重复的代码都应该要上升到父类去，而不是让每个子类都去重复。模板方法模式就是通过把不变的行为搬移到超类，去除子类中的重复代码来体现它的优势。在软件开发过程中，如果相同的一段代码复制过两次，就需要对设计产生怀疑。</span></div></li><li><div><span style="font-size: 12pt;">当我们要完成在某一细节层次一致的一个过程或一系列步骤，但其个别步骤在更详细的层次上的实现可能不同时，我们通常考虑用模板方法模式来处理。父类建立框架，子类在重写了父类部分的方法后，再调用从父类继承的方法，产生了不同的结果，从而使得相同的算法框架可以有不同的执行结果。</span></div></li><li><div><span style="font-size: 16px;">模板方法模式提供了一个很好的代码复用平台。</span></div></li><li><div><span style="font-size: 12pt;">某个方法的实现需要多个步骤，其中有些步骤是固定的，而有些步骤并不固定，存在可变性。为了提高代码的复用性和系统的灵活性，可以选择使用模板方法模式。</span><span style="font-size: 12pt; color: unset; font-family: unset;">当不变的和可变的行为在方法的子类实现中混合在一起的时候，不变的行为就会在子类中重复岀现。可以通过模板方法模式把这些行为搬移到单一的地方，这样就帮助子类摆脱重复的不变行为的纠缠。</span></div></li><li><div><font style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">适用性</span></font></div></li><ul><li><div><span style="font-size: 12pt;">有时候会遇到由一系列步骤构成的算法需要执行，</span><span style="font-size: 12pt;">对</span><span style="font-size: 12pt;">重要、</span><span style="font-size: 12pt;">复杂的算法进行分割，</span><span style="font-size: 12pt;">这个算法从高层次上看是相同的，但有些步骤的实现可能不同。</span><span style="font-size: 12pt;">可以把核心算法设计为模板方法，</span><span style="font-size: 12pt;">将其算法中固定不变的部分设计为模板方法和父类具体方法，而</span><span style="font-size: 12pt;">一些可以改变的细节及</span><span style="font-size: 12pt;">周边的相关细节功能</span><span style="font-size: 12pt;">由其子类来实现。</span><span style="font-size: 12pt;">即：一次性实现一个算法的不变部分，并将可变的行为留给子类来实现。</span></div></li><li><div><span style="font-size: 12pt;">各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。</span><span style="font-size: 12pt;">多个子类有公有的方法，并且逻辑基本相同时。</span><span style="font-size: 12pt;">各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是“重分解以一般化”的一个很好的例子。首先识别现有代码中的不同之处，并且将不同之处分离为新的操作。最后，用一个调用这些新的操作的模板方法来替换这些不同的代码。</span></div></li><li><div><span style="font-size: 12pt;">控制子类扩展。模板方法只在特定点调用“hook”操作，这样就只允许在这些点进行扩展。</span></div></li><li><div><span style="font-size: 12pt;">需要通过子类来决定父类算法中某个步骤是否执行，实现子类对父类的反向控制。</span><span style="font-size: 12pt;">重构时，模板方法模式是一个经常使用的模式，把相同的代码抽取到父类中，然后通过钩子函数约束其行为。</span></div></li></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">实现上的考虑</span></span></div></li><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">使用访问控制</span></span></div><div><span style="font-size: 12pt;">一个模板方法调用的原语操作可以被定义为 protected 成员。</span><span style="font-size: 12pt; color: unset; font-family: unset;">这保证它们只被模板方法调用。必须重定义的原语操作须定义为纯虚函数。模板方法自身不需被重定义，因此可以将模板方法定义为一个非虚成员函数。</span></div></li><li><div><font style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">尽量减少原语操作</span></font></div><div><span style="font-size: 12pt;">定义模板方法的一个重要目的是尽量减少一个子类具体实现该算法时必须重定义的那些原语操作的数目。需要重定义的操作越多，客户程序就越冗长。</span></div></li><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">命名约定</span></span></div><div><span style="font-size: 12pt;">可以给应被重定义的那些操作的名字加上一个前级以识别它们。</span></div></li><li><div><span style="font-size: 16px;">为防止子类改变模板方法中的算法，可以将模板方法声明为final。</span></div></li></ul><li><div><span style="font-size: 16px;">模板方法模式的实际应用：</span></div></li><ul><li><div><span style="font-size: 12pt; color: unset; font-family: unset;">模板方法模式是基于继承的代码复用技术，</span><span style="font-size: 16px; color: unset; font-family: unset;">仅使用了继承机制，</span><span style="font-size: 12pt; color: unset; font-family: unset;">它体现了面向对象的诸多重要思想，是一种使用较为频繁的模式。</span><span style="font-size: 16px; color: unset; font-family: unset;">几乎可以在任何一个抽象类中找到。对继承和多态有一定认识的人几乎都会在继承体系中使用到模板方法模式。</span></div></li><li><div><span style="font-size: 12pt;">模板方法模式广泛应用于框架设计中，以确保通过父类来控制处理流程的逻辑顺序（如框架的初始化，测试流程的设置等）。</span></div><div><span style="font-size: 12pt;">由框架控制如何做事情，由用户指定算法中每个步骤的实现。</span></div></li><li><div><span style="font-size: 16px;">真实世界的代码中有很多模板方法模式的变体。</span></div></li></ul><li><div><span style="font-size: 16px;">与其他模式的关系</span></div></li><ul><li><div><span style="font-size: 16px;">策略模式和模板方法模式都封装算法，一个用组合，一个用继承</span></div></li><li><div><span style="font-size: 16px;">工厂方法是模板方法的一个特殊版本。</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="22.模板方法（Template Method）模式_files/Image.png" type="image/png" data-filename="Image.png"/></div><ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">AbstractClass（抽象类）：</span></span></div></li><ul><li><div><span style="font-size: 16px;">模板方法模式的核心是抽象类和其中模板方法的设计。</span></div></li><li><div><span style="font-size: 12pt;">定义抽象的原语操作（ primitive operation），具体的子类将重定义它们以实现一个算法的各步骤。模板方法本身和这两个操作的具体实现之间被解耦了。</span></div></li><li><div><span style="font-size: 12pt;">实现一个模板方法（template method），定义一个算法的骨架，完成固定的逻辑。该模板方法不仅调用原语操作，也调用定义在  Abstractclass 或其他对象中的操作。提供一些抽象的行为，放到子类中实现。</span><span style="font-size: 12pt;">这个模板方法一般是一个具体方法，给出了一个顶级逻辑的框架，而逻辑的组成步骤在相应的抽象操作中，推迟到子类实现（指元语操作），顶级逻辑也有可能调用一些具体方法。</span></div></li><li><div><span style="font-size: 16px;">为防止恶意操作，Java 中，模板方法都加上 final 关键字，不能覆写，而原语操作尽量设计为 protected 类型。</span></div></li></ul><li><div><span style="font-size: 16px;"><span style="font-size: 16px; font-weight: bold;">ConcreteClass（具体类）：</span></span></div></li><ul><li><div><span style="font-size: 16px;">实现原语操作以完成算法中不变的步骤。实现具体的逻辑步骤。</span></div></li><li><div><span style="font-size: 16px;">具体类中也可以覆写父类中已经实现的具体操作。</span></div></li><li><div><span style="font-size: 12pt;">实现父类定义的一个或多个抽象方法，每一个 AbstractClass 都可以有任意多个 ConcreteClass 与之对应，而每一个 ConcreteClass 都可以给出这些抽象方法的不同实现，从而使得顶级逻辑的实现各不相同，其实也是一个抽象模板。</span></div></li></ul><li><div><span style="font-size: 12pt; font-weight: bold;">协作：</span></div></li><ul><li><div><span style="font-size: 12pt;">ConcreteClass 靠 AbstractClass 来实现算法中不变的步骤。</span></div></li><li><div><span style="font-size: 16px;">AbstractClass 靠 ConcreteClass 来实现具体的一些操作。</span></div></li></ul></ul><div><span style="font-size: 12pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-size: 12pt; font-weight: bold;">优点</span></div><ul><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">封装不变部分，扩展可变部分。</span></span><span style="font-size: 12pt;">模板方法模式是一种代码复用技术，它在类库设计中尤为重要，它提取了类库中的公共行为，将公共行为放在父类中，而通过其子类来实现不同的行为，</span><span style="font-size: 12pt;">通过继承来继续扩展。</span><span style="font-size: 12pt;">它鼓励我们恰当使用继承来实现代码复用。</span></div></li><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">提取公共部分代码，便于维护。</span></span></div></li><li><div><span style="font-size: 12pt;"><span style="font-size: 12pt; font-weight: bold;">行为由父类控制，子类实现。</span></span></div></li><ul><li><div><span style="font-size: 12pt;">在父类中形式化地定义一个算法，而由它的子类来实现细节的处理，在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。</span></div></li><li><div><span style="font-size: 12pt;">基本方法是由子类实现的，因此子类可以通过扩展的方式增加相应的功能，符合开闭原则。</span></div></li></ul><li><div><span style="font-size: 12pt;">通过使用模板方法模式，一方面提高了代码的</span><span style="font-size: 12pt; font-weight: bold;">复用性</span><span style="font-size: 12pt;">，另一方面还可以利用面向对象的多态性，在运行时选择一种具体子类，提高系统的</span><span style="font-size: 12pt; font-weight: bold;">灵活性</span><span style="font-size: 12pt;">和</span><span style="font-size: 12pt; font-weight: bold;">可扩展性</span><span style="font-size: 12pt;">。</span></div></li><li><div><span style="font-size: 12pt;">可实现一种</span><span style="font-size: 12pt; font-weight: bold;">反向控制结构</span><span style="font-size: 12pt;">，通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行。</span></div></li><li><div><span style="font-size: 12pt;">在模板方法模式中可以通过子类来覆盖父类的基本方法，不同的子类可以提供基本方法的不同实现，更换和增加新的子类很方便，符合</span><span style="font-size: 12pt; font-weight: bold;">单一职责原则</span><span style="font-size: 12pt;">和</span><span style="font-size: 12pt; font-weight: bold;">开闭原则</span><span style="font-size: 12pt;">。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">缺点</span></div><ul><li><div><span style="font-size: 12pt;">按一般的设计习惯，抽象类负责声明最抽象、最一般的事物属性和方法，实现类完成具体的事物属性和方法。但是模板方法模式却颠倒了，抽象类定义了部分抽象方法，由子类实现，子类执行的结果影响了父类的结果，也就是子类对父类产生了影响，这在复杂的项目中，会带来代码阅读的难度。</span></div></li><li><div><span style="font-size: 12pt;">需要为每一个基本方法的不同实现提供一个子类，如果父类中可变的基本方法太多，将会导致类的个数增加，系统更加庞大，设计也更加抽象，此时，可结合桥接模式来进行设计。</span></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">四、可能的改进</span></div><ul><li><div><a href="https://github.com/PhycheLiu/DesignPattern_Study-Test/tree/master/Java/src/com/company/strategy/sample01" style="font-size: 16px; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;; font-variant-caps: normal; font-variant-ligatures: normal;">可以利用“xml + 反射”机制改进策略模式。在 xml 当中指定使用哪个具体策略。</a></div></li></ul><div><span style="font-size: 12pt; font-weight: bold;">五、个人理解</span></div><ul><li><div><span style="font-size: 16px;">模板方法模式非常简单，应用非常广泛，日常编程的时候经常会用到，尤其是其中提取公共代码的部分。</span></div></li><li><div><span style="font-size: 16px;">实际上感觉模板方法模式最精髓的部分应该是其中的模板方法。这个模板方法在很多框架中可以看到，并且大量应用过，这个模式非常实用，可以尝试实践中应用一下。</span></div></li><li><div><span style="font-size: 16px;">钩子方法其实也没有那么神秘，这个模式当中的论述还是非常清晰的。</span></div></li><li><div><span style="font-size: 16px;">基类中定义的基本方法有三类，个人的理解：</span></div></li><ul><li><div><span style="font-size: 16px;">抽象方法：这里的抽象方法实际上就是《</span><span style="font-size: 12pt;">设计模式：可复用面向对象软件的基础</span><span style="font-size: 16px;">》书中提到的“原语操作”，主要体现动态绑定，体现不同的子类不同实现，子类中必须要重新实现这类方法。</span></div></li><li><div><span style="font-size: 16px;">具体方法：主要体现将子类中公共操作提取到基类当中，基类中实现这个方法，子类中可以覆盖这个方法并且重新实现，但是一般不会修改基类中的实现。</span></div></li><li><div><span style="font-size: 16px;">钩子方法：一个特殊的具体方法，基类中实现这个方法，但是子类中可能会修改基类当中的实现，体现子类对基类的反向控制，也体现了不同的子类实现不同。</span></div><div><span style="font-size: 16px;">钩子方法与“回调”的概念非常相似，只不过实现机制和实现目的有一些不同。一般的回调函数使用函数指针或者函数对象实现，如果不提供具体的实现，则编译报错。而钩子方法强调的是子类当中可以实现，也可以不实现，不实现使用基类中的默认实现，此时编译正常。</span></div></li></ul></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br/></div><div><br/></div></div><div><br/></div></span>
</div></body></html> 